/* Hawk - An OSGI Based Test Framework
 *
 * Copyright (C) 2010 http://code.google.com/p/hawk-framework/
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 */
package org.hawk.core.engine;

import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import java.util.Vector;
import java.util.logging.ConsoleHandler;
import java.util.logging.FileHandler;
import java.util.logging.Logger;

import org.hawk.core.interfaces.TestEngine;
import org.hawk.core.interfaces.TestService;
import org.hawk.core.log.ConsoleFormatter;
import org.hawk.core.log.FileFormatter;
import org.hawk.core.model.Bundle;
import org.hawk.core.model.File;
import org.hawk.core.model.Input;
import org.hawk.core.model.Output;
import org.hawk.core.model.Parameter;
import org.hawk.core.model.Session;
import org.hawk.core.model.Step;
import org.hawk.core.model.TestCase;
import org.hawk.core.model.Value;
import org.hawk.core.model.Variable;
import org.hawk.core.util.FileIO;
import org.hawk.core.xml.TestSessionParser;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.xml.sax.SAXException;

/**
 * This class is responsible to execute the test session. A test session is 
 * composed by test cases. Each test case has a set of test steps. Each test 
 * step executes a service of a OSGI bundle. All test session configuration 
 * is described in a standard XML file. 
 * 
 * Note: actually in this first version this class loads the test session 
 * from hawk.xml file. The file must be stored in the directory appointed
 * by HAWK_HOME system environment
 *  
 * 
 * @author Rodrigo Prestes Machado
 * @version November 16, 2009
 */
public class Engine extends Thread implements TestEngine {

	// A variable collection
	private Collection<Variable> variables;

	// Bundle context from Equinox framework
	private BundleContext context;

	// Logger
	private Logger log;

	// The log file handler
	private FileHandler fileHandler;
	
	// The log console handler
	private ConsoleHandler consoleHandler;

	// Hawk log fine name
	private static final String LOG_FILE_NAME = "hawkLog.html";

	// Hawk Test Session
	private static final String TEST_SESSION_FILE = "hawk.xml";
	
	// Store the actual directory
	public static String hawkHome;

	/**
	 * Class constructor
	 * 
	 * @param BundleContext context
	 */
	public Engine(BundleContext context) {
		
		super("Hawk Test Engine");
		this.context = context;
		this.variables = new Vector<Variable>();
		
		// If the HAWK_HOME variable wasn't configured we try to get the local path  
		if (hawkHome == null){	
			try {
				java.io.File file = new java.io.File(".");
				hawkHome = (String) file.getCanonicalPath();
			} 
			catch (IOException e) {
				log.warning("hawk.xml not found");
			}
			
		}
		createLog();
	}

	/**
	 * Thread run method
	 */
	public void run() {
		try {
			TestSessionParser parser = new TestSessionParser( hawkHome + java.io.File.separator + TEST_SESSION_FILE);
			executeSession(parser.parse());
		} 
		catch (SAXException e) {
			log.warning("The hawk.xml file has a sintax error");
		} 
		catch (IOException e) {
			log.warning("hawk.xml not found");
		}
	}
	
	/**
	 * Description : This method is able to create the system log objects
	 */
	private void createLog() {
		try {
			// Creating a log object
			log = Logger.getLogger("org.hawk");
			
			fileHandler = new FileHandler(hawkHome + java.io.File.separator + LOG_FILE_NAME);
			// Creating a file handler
			consoleHandler = new ConsoleHandler();
			
			// Creating a new formatter
			fileHandler.setFormatter(new FileFormatter());
			// Creating a console formatter
			consoleHandler.setFormatter(new ConsoleFormatter());
			// Add a file handler in log object
			log.addHandler(fileHandler);
			//log.addHandler(consoleHandler);
			
		} 
		catch (SecurityException e) {
			log.warning("SecurityException: the log file has not been created");
		} 
		catch (IOException e) {
			log.warning("IOException: the log file has not been created");
		}
		finally{
			//threadDone = true;
		}
	}

	/**
	 * Executes a test session
	 * 
	 * @param A session object from Hawk test model
	 */
	private void executeSession(Session session) {

		Collection<TestCase> testCases = session.getTestCases();
		Iterator<TestCase> testCasesIterator = testCases.iterator();

		while (testCasesIterator.hasNext()) {
			TestCase testcase = (TestCase) testCasesIterator.next();
			log.info(testcase.getName());
			log.info(testcase.getDescription());
			executeTestCase(testcase);
		}

		vizualizeResults(session);

	}

	/**
	 * Executes a test case
	 * 
	 * @param A test case from Hawk Test Model
	 */
	private void executeTestCase(TestCase testCase) {
		Collection<Step> steps = testCase.getSteps();
		Iterator<Step> stepsIterator = steps.iterator();

		while (stepsIterator.hasNext()) {
			Step step = stepsIterator.next();
			log.info(step.getDescription());
			String result = executeTestBundle(step.getBundle());

			if (result == null) {
				testCase.setResult(false);
				break;
			} 
			else {
				testCase.setResult(Boolean.valueOf(result));
			}
		}
	}

	/**
	 * Executes a service of a bundle 
	 * 
	 * @param A bundle according Hawk Test Model
	 * @return The result of execution
	 */
	private String executeTestBundle(Bundle bundle) {

		String serviceName = bundle.getServiceName();
		ServiceReference reference = context.getServiceReference(serviceName);
		
		if (reference == null) {
			log.warning("The service " + serviceName + " is not registrated");
		}

		Input input = arrangeVariables(bundle.getInput());
		input = arrangeFiles(input);
		String result = null;

		try {
			TestService service = (TestService) context.getService(reference);
			result = service.execute(bundle.getInput(), log);
		} 
		catch (Exception e) {
			log.warning("Bundle Exception. The test case will fail");
			return null;
		}

		if (hasOutput(bundle)) {
			createOutput(result, bundle.getOutput());
		}

		return result;

	}
	
	/**
	 * Shows all test session results
	 * 
	 * @param A test session according Hawk Test Model
	 */
	private void vizualizeResults(Session session) {

		Vector<TestCase> testCases = (Vector<TestCase>) session.getTestCases();
		log.info("Executed Test Cases: " + testCases.size());

		int totalPass = 0;
		int totalFail = 0;
		boolean result;

		for (int i = 0; i < testCases.size(); i++) {
			TestCase testCase = (TestCase) testCases.get(i);
			result = testCase.getResult();
			if (result == true)
				totalPass++;
			else
				totalFail++;
		}

		log.info("PASSED: " + totalPass);
		log.info("FAILED: " + totalFail);
	}

	/**
	 * This method is used to read the parameter files objects from disk
	 * 
	 * @param An input object
	 * @return An input object that has files objects with values
	 */
	private Input arrangeFiles(Input input) {

		if (Parameter.existsFiles(input)) {

			Collection<File> inputFiles = Parameter.getParameterFile(input);
			Iterator<File> inputFilesIterator = inputFiles.iterator();

			while (inputFilesIterator.hasNext()) {

				File inputFile = (File) inputFilesIterator.next();
				String inputFileName = inputFile.getName();

				String finalFilename = arrangeFileName(inputFileName);

				String fileContent = FileIO.readFile(finalFilename);
				inputFile.setValue(fileContent);

			}
		}
		return input;
	}

	/**
	 * This method is responsible to set the variables into the input object
	 * 
	 * @param An input object with variables objects
	 * @return An input with only values objects
	 */
	private Input arrangeVariables(Input input) {
		if (Parameter.existsVariables(input)) {

			Collection<Variable> inputVariables = Parameter.getParameterVariable(input);
			Iterator<Variable> inputVariablesIterator = inputVariables.iterator();

			while (inputVariablesIterator.hasNext()) {

				Variable inputVariable = (Variable) inputVariablesIterator.next();
				String inputVariableName = inputVariable.getName();
				Iterator<Variable> storedVariablesIterator = this.variables.iterator();

				while (storedVariablesIterator.hasNext()) {

					Variable storevariable = (Variable) storedVariablesIterator.next();
					String storedVariableName = storevariable.getName();

					if (storedVariableName.equals(inputVariableName)) {
						Value value = new Value(storevariable.getValue());
						inputVariable.setValue(value.getValue());
					}
				}
			}
		}
		return input;
	}

	/**
	 * This method verifies if a bundle has an output
	 * 
	 * @param A bundle object
	 * @return True if the bundle has variables
	 */
	private boolean hasOutput(Bundle bundle) {
		boolean result = false;
		if (bundle.getOutput() != null)
			result = true;
		return result;
	}

	/**
	 * This method is responsible to create the output. The output can be used 
	 * to create variables objects or write the result into the hard disk
	 * 
	 * @param The result from the concrete bundle
	 * @param The output object from the bundle
	 */
	private void createOutput(String result, Output output) {

		if (output.getFile() != null) {
			String fileName = output.getFile();

			String finalFilename = arrangeFileName(fileName);

			FileIO.writeFile(finalFilename, result);
		}

		if (output.getVariable() != null) {
			Variable variable = new Variable(result);
			variable.setName(output.getVariable());
			this.variables.add(variable);
		}
	}

	/**
	 * The method concatenates the file name from XML with HAWK_HOME directory
	 * 
	 * @param The file name from XML file
	 * @return A file name from XML file with the HAWK_HOME directory
	 */
	private String arrangeFileName(String fileName) {
		String finalFileName = hawkHome + java.io.File.separator + fileName;
		return finalFileName;
	}

	/**
	 * Description : Indicates that the thread is done
	 */
	public void done() {
		//threadDone = true;
	}

}